﻿#region Header
// ------------------------ Licence / Copyright ------------------------
// 
// ScrumTable for Scrum Meeting Support
// Copyright © HSR - Hochschule für Technik Rapperswil
// All Rights Reserved
// 
// Author:
//  Michael Gfeller Silvan Gehrig Patrick Boos
// 
// ---------------------------------------------------------------------
#endregion

#region Usings

using System;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
using Microsoft.TeamFoundation.Common;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
using ScrumTable.Common;
using ScrumTable.Common.Collections;
using ScrumTable.Common.Logging;
using ScrumTable.DL.Data.TFS.Properties;

#endregion

namespace ScrumTable.DL.Data.TFS.Services.TeamFoundation
{
    /// <summary>
    /// This class provides additional functionalities for work item
    /// store handling.
    /// </summary>
    public static class TfsWorkItemStore
    {
        #region Declarations
        //--------------------------------------------------------------------
        // Declarations
        //--------------------------------------------------------------------

        #endregion

        #region Properties
        //--------------------------------------------------------------------
        // Properties
        //--------------------------------------------------------------------

        #endregion

        #region Constructors / Destructor
        //--------------------------------------------------------------------
        // Constructors / Destructor
        //--------------------------------------------------------------------

        #endregion

        #region Methods
        //--------------------------------------------------------------------
        // Methods
        //--------------------------------------------------------------------

        /// <summary>
        /// Gets the field information based on the given schema entry instance.
        /// </summary>
        /// <param name="input">Specifies the work item store pointer, must not be null.</param>
        /// <param name="query">Specifies the query to execute.</param>
        /// <param name="parameters">Specifies a set of parameters which are passed to the query engine.</param>
        /// <returns>Returns the retrieved field or a null reference if it could not be found.</returns>
        public static IList<WorkItem> QueryWorkItems(this WorkItemStore input, string query, IDictionary parameters)
        {
            PreCondition.AssertNotNull(input, "input");

            if (!string.IsNullOrEmpty(query))
            {
                return input.Query(query, parameters).Convert(wi => (WorkItem)wi);    
            }
            return new List<WorkItem>(0);
        }

        /// <summary>
        /// Gets the field information based on the given schema entry instance.
        /// </summary>
        /// <param name="input">Specifies the work item store pointer, must not be null.</param>
        /// <param name="query">Specifies the query to execute.</param>
        /// <param name="parameters">Specifies a set of parameters which are passed to the query engine.</param>
        /// <returns>Returns the retrieved field or a null reference if it could not be found.</returns>
        public static IDictionary<int, WorkItem> QueryWorkItemsAndIndex(this WorkItemStore input, WiqlQueryBuilder query, IDictionary parameters)
        {
            PreCondition.AssertNotNull(input, "input");

            if (query != null)
            {
                string queryString = query.ToString();

                if (!string.IsNullOrEmpty(queryString))
                {
                    return input.Query(queryString, parameters).Convert(wi => new KeyValuePair<int, WorkItem>(((WorkItem)wi).Id, (WorkItem)wi));    
                }
            }
            return new Dictionary<int, WorkItem>(0);
        }


        /// <summary>
        /// Tries to save the specified work items back to the WorkItemStore.
        /// </summary>
        /// <param name="input">Specifies the work item store pointer, must not be null.</param>
        /// <param name="workItems">Specifies a set of work items which contain to updated values.</param>
        /// <param name="onError">Specifies an error action which is executed if a work item could not be saved.</param>
        public static void TrySave(this WorkItemStore input, IEnumerable<WorkItem> workItems, Action<Exception, WorkItem, ICollection<Field>> onError)
        {
            PreCondition.AssertNotNull(input, "input");
            PreCondition.AssertNotNull(workItems, "workItems");
            PreCondition.AssertNotNull(onError, "onError");
            
            // important: force a COPY of the list into an array
            WorkItem[] workItemsToSave = ValidateWorkItems(workItems, onError);

            if (workItemsToSave.Length > 0)
            {
                // save new workitems back to TFS
                BatchSaveError[] errors = input.BatchSave(workItemsToSave, SaveFlags.MergeLinks);

                // handle errors
                if (errors != null && errors.Length > 0)
                {
                    foreach (BatchSaveError saveError in errors)
                    {
                        Logger.Info(
                            string.Format(Resources.ExcTfsWiStoreSaveFailed, saveError.WorkItem, saveError.WorkItem.Id),
                            saveError.Exception);

                        // an error occurred during the creation of the new objects, let's
                        // report this with the event
                        onError(
                            saveError.Exception,
                            saveError.WorkItem,
                            saveError.WorkItem.Validate().Convert<Field>());
                    }
                }
            }
        }

        private static WorkItem[] ValidateWorkItems(IEnumerable<WorkItem> workItems, Action<Exception, WorkItem, ICollection<Field>> onError)
        {
            IList<WorkItem> validElements = workItems.ToList();
            int validElementCount = validElements.Count;

            // validate all elements
            foreach (var workItem in workItems)
            {
                ICollection<Field> errors = workItem.Validate().Convert<Field>();

                if (errors.Count > 0)
                {
                    // a validation error occurred, so ScrumTable must not store
                    // any of the data
                    onError(null, workItem, errors);
                    validElements.Remove(workItem);
                }
            }

            // force rollback of ALL element if one validation progress failed
            if (validElements.Count != validElementCount)
            {
                foreach (var workItem in validElements)
                {
                    onError(null, workItem, new Field[0]);
                }
                return new WorkItem[0];
            }
            return workItems.ToArray();
        }

        #endregion

        #region Events
        //--------------------------------------------------------------------
        // Events
        //--------------------------------------------------------------------

        #endregion
    }
}